/*
 * Copyright 1999-2004 Alibaba.com All right reserved. This software is the confidential and proprietary information of
 * Alibaba.com ("Confidential Information"). You shall not disclose such Confidential Information and shall use it only
 * in accordance with the terms of the license agreement you entered into with Alibaba.com.
 */
package action.expr;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import action.exception.ActionException;
import action.expr.ast.Action;
import action.expr.ast.Addend;
import action.expr.ast.Assignator;
import action.expr.ast.Assignment;
import action.expr.ast.Assignments;
import action.expr.ast.CustomOp;
import action.expr.ast.Expr;
import action.expr.ast.Factor;
import action.expr.ast.Field;
import action.expr.ast.OpLevel1;
import action.expr.ast.PrimaryLevel2Op;

/**
 * 
 * Action expression parser
 * 
 * @author pf-miles 2011-6-21 下午01:46:17
 * 
 */
public class ActionParser {
    private ActionLexer lexer;

    // buffered tokens that were look-ahead
    private List<ActionToken> aheadBuffer = new ArrayList<ActionToken>();
    // current token in buffer position, -1 when no tokens in buffer
    private int p = -1;

    public ActionParser(ActionLexer lexer) {
        this.lexer = lexer;
    }

    public Action action() {
        Action ret = new Action();
        ret.setAssignments(this.assignments());
        match(ActionTokenType.EOF);
        return ret;
    }

    public Assignments assignments() {
        Assignments ret = new Assignments();
        do {
            ret.getAs().add(this.assignment());
        } while (this.LT(1).getType() != ActionTokenType.EOF);
        return ret;
    }

    public Assignment assignment() {
        Assignment a = new Assignment();
        a.setField(this.field());
        switch (this.LT(1).getType()) {
        case EQ:
            this.match(ActionTokenType.EQ);
            a.setAssignator(Assignator.EQ);
            break;
        case ADD_EQ:
            this.match(ActionTokenType.ADD_EQ);
            a.setAssignator(Assignator.ADD_EQ);
            break;
        case MINU_EQ:
            this.match(ActionTokenType.MINU_EQ);
            a.setAssignator(Assignator.MINU_EQ);
            break;
        case MUL_EQ:
            this.match(ActionTokenType.MUL_EQ);
            a.setAssignator(Assignator.MUL_EQ);
            break;
        case DIV_EQ:
            this.match(ActionTokenType.DIV_EQ);
            a.setAssignator(Assignator.DIV_EQ);
            break;
        default:
            throw new ActionException("Unexpected token in assignment: " + this.LT(1));
        }
        a.setExpr(this.expr(false));
        return a;
    }

    public Field field() {
        Field ret = new Field();
        ActionToken t = this.match(ActionTokenType.KEY);
        ret.setKey(t.getValue());
        return ret;
    }

    public Expr expr(Object... param) {
        Expr ret = new Expr();
        if ((Boolean) param[0])
            ret.setQuoted(true);
        Addend a = this.addend();
        switch (this.LT(1).getType()) {
        case ADD:
            ret.setLeft(a);
            this.match(ActionTokenType.ADD);
            ret.setOp(OpLevel1.ADD);
            ret.setRight(this.addend());
            break;
        case MINU:
            ret.setLeft(a);
            this.match(ActionTokenType.MINU);
            ret.setOp(OpLevel1.MINU);
            ret.setRight(this.addend());
            break;
        default:
            ret.setSingle(a);
            break;
        }
        return ret;
    }

    public Addend addend() {
        Addend a = new Addend();
        Factor f = this.factor();
        ActionTokenType t = this.LT(1).getType();
        ActionTokenType t2 = null;
        if (t != ActionTokenType.EOF) {
            t2 = this.LT(2).getType();
        }
        if (t == ActionTokenType.MUL) {
            a.setLeft(f);
            this.match(ActionTokenType.MUL);
            a.setOp(PrimaryLevel2Op.MUL);
            a.setRight(this.factor());
        } else if (t == ActionTokenType.DIV) {
            a.setLeft(f);
            this.match(ActionTokenType.DIV);
            a.setOp(PrimaryLevel2Op.DIV);
            a.setRight(this.factor());
        } else if (t == ActionTokenType.KEY && t2 != ActionTokenType.EQ && t2 != ActionTokenType.ADD_EQ
                && t2 != ActionTokenType.MINU_EQ && t2 != ActionTokenType.MUL_EQ
                && t2 != ActionTokenType.DIV_EQ) {
            a.setLeft(f);
            a.setOp(this.customOp());
            a.setRight(this.factor());
        } else {
            a.setSingle(f);
        }
        return a;
    }

    public Factor factor() {
        Factor f = new Factor();
        switch (this.LT(1).getType()) {
        case NUM:
            ActionToken t = this.match(ActionTokenType.NUM);
            f.setNum(new BigDecimal(t.getValue()));
            break;
        case STR:
            t = this.match(ActionTokenType.STR);
            f.setStr(t.getValue());
            break;
        case KEY:
            f.setField(this.field());
            break;
        case LP:
            this.match(ActionTokenType.LP);
            f.setExpr(this.expr(true));
            this.match(ActionTokenType.RP);
            break;
        default:
            throw new ActionException("Unexpected token in factor: " + this.LT(1));
        }
        return f;
    }

    public CustomOp customOp() {
        CustomOp op = new CustomOp();
        ActionToken t = this.match(ActionTokenType.KEY);
        op.setKey(t.getValue());
        return op;
    }

    /**
     * 
     * match a token by tokenType, actually consumes a token
     * 
     * @param type
     * @return the matched token
     */
    public ActionToken match(ActionTokenType type) {
        ActionToken t = this.nextToken();
        if (t.getType() != type) {
            throw new ActionException("Unexpected token: " + t + ", " + type + " expected.");
        } else {
            return t;
        }
    }

    // lookahead by 'ahead', cache tokens in buffer while lookahead
    private ActionToken LT(int ahead) {
        Iterator<ActionToken> it = lexer.iterator();
        if (p == -1) {
            // should load from lexer until 'ahead'
            for (int i = 0; i < ahead; i++) {
                aheadBuffer.add(it.next());
            }
            p = 0;
        } else {
            if (p + ahead > aheadBuffer.size()) {
                /*
                 * should load from lexer until the look-ahead token is in
                 * buffer
                 */
                int more = p + ahead - aheadBuffer.size();
                for (int i = 0; i < more; i++) {
                    aheadBuffer.add(it.next());
                }
            }
        }
        return aheadBuffer.get(p + ahead - 1);
    }

    // if there's tokens buffered, try to read the buffer first
    private ActionToken nextToken() {
        Iterator<ActionToken> it = lexer.iterator();
        if (p == -1) {
            if (it.hasNext()) {
                return it.next();
            } else {
                throw new ActionException("No more token.");
            }
        } else {
            ActionToken ret = aheadBuffer.get(p);
            if (p == aheadBuffer.size() - 1) {
                // last one in buffer
                p = -1;
                aheadBuffer.clear();
            } else {
                p += 1;
            }
            return ret;
        }
    }
}
